// Copyright (c) 2009 - Quildreen <quildreen@gmail.com>                     
//                                                                          
//  Permission is hereby granted, free of charge, to any person              
//  obtaining a copy of this software and associated documentation files     
//  (the "Software"), to deal in the Software without restriction,           
//  including without limitation the rights to use, copy, modify, merge,     
//  publish, distribute, sublicense, and/or sell copies of the Software,     
//  and to permit persons to whom the Software is furnished to do so,        
//  subject to the following conditions:                                     
//                                                                           
//  The above copyright notice and this permission notice shall be           
//  included in all copies or substantial portions of the Software.          
//                                                                           
//  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,          
//  EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF       
//  MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND                    
//  NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE   
//  LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION   
//  OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION    
//  WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

/**
 * lily.preloader
 * 
 * The preloader module implements the resource management used in
 * [[lily]]. This include preloading general data and image, and tracking the
 * data being loaded in the framework. It also provides abstract data classes
 * that can be extended to add support to other kind of data.
 *
 * Most functionality is wrapped in the [[lily.preloader.Loader]] class, that
 * ties the single resource tracking together. Single data classes are not
 * meant to be instantiated directly, instead create a
 * [[lily.preloader.Loader]] object and add the resource in it.
 **/
lily.defineModule("preloader");


/**
 * class lily.preloader.AbstractResource
 * 
 * This represents an abstract resource in [[lily]]. It is not meant to be
 * instanciated directly, but rather extended to implement more specific
 * resource tracking.
 *
 * The [[lily.preloader.AbstractResource._progress]] implements the loading
 * progress logic, and all you have to do is call this function whenever the
 * resource you're loading receives new chunks of data.
 *
 * When the resource finishes loading, calling
 * [[lily.preloader.AbstractResource._finish]] will tell the
 * [[lily.preloader.Loader]] object about its completion and also fire the
 * [[lily.preloader.AbstractResource.onFinish]] callback.
 *
 * To keep it simple, all you have to do when extending this class is overwrite
 * the [[lily.preloader.AbstractResource.start]] method, which should start
 * loading your resource, and make this resource call the
 * [[lily.preloader.AbstractResource._progress]] and
 * [[lily.preloader.AbstractResource.finish]] methods.
 **/
lily.preloader.AbstractResource = Class.create(
{
    /**
     * lily.preloader.AbstractResource.__kind__ = "Abstract"
     * 
     * The kind of resource handled by this class.
     **/
    __kind__: "Abstract",


    /**
     * new lily.preloader.AbstractResource(preloader[, on_progress][, on_finish])
     * - preloader ([[lily.preloader.Loader]]): An instance of the Loader
     * that's managing this resource.
     * - on_progress (Function): optional callback for each update of data
     * received.
     * - on_finish (Function): optional callback for when resource's finished
     * loading.
     **/
    initialize: function(preloader, on_progress, on_finish)
    {
        /**
         * lily.preloader.AbstractResource#id -> Number (read only)
         * 
         * An unique number that identifies this instance
         **/
        this.id = lily.uniqueID();


        /**
         * lily.preloader.AbstractResource#preloader -> lily.preloader.Loader
         * 
         * The Loader object that's managing this resource
         **/
        this.owner = preloader;

        /**
         * lily.preloader.AbstractResource#bytes_loaded -> Number
         * 
         * The number of bytes of this resource that has been loaded.
         **/
        this.bytes_loaded = 0;

        /**
         * lily.preloader.AbstractResource#bytes_total -> Number
         * 
         * The total size of this resource, in bytes.
         **/
        this.bytes_total = 0;

        /**
         * lily.preloader.AbstractResource#old_bytes_loaded -> NUmber
         * 
         * The number of bytes that has been loaded until the previous update.
         **/
        this.old_bytes_loaded = 0;

        /**
         * lily.preloader.AbstractResource#old_bytes_total -> Number
         * 
         * The total size of this resource until the previous update.
         **/
        this.old_bytes_total = 0;

        /**
         * lily.preloader.AbstractResource#onProgress -> Function
         * 
         * A callback for each update of data received, with the following
         * signature:
         * 
         *      function(bytes_loaded, bytes_total)
         * 
         * - `bytes_loaded` (Number): the number of bytes loaded until this
         * update.
         * - `bytes_total` (Number): the total size of the resource until this
         * update.
         **/
        this.onProgress = on_progress || function(){};

        /**
         * lily.preloader.AbstractResource#onFinish -> Function
         * 
         * A callback that's called, with no parameters, when the resource
         * finishes loading.
         **/
        this.onFinish = on_finish || function(){};
    },


    /**
     * lily.preloader.AbstractResource#_progress(bytes_loaded, bytes_total) -> undefined
     * - bytes_loaded (Number): total of bytes already loaded.
     * - bytes_total (Number): size of this resource, in bytes.
     * 
     * Internally called at each update of received data (i.e.: new chunks of
     * data received).
     * 
     * Since the actual loading is implementation dependent, this tries to be
     * the most abstract possible.
     * 
     * Refer to the [[lily.preloader.AbstractResource]] reference for
     * information on how to extend the class and implement your own resource
     * loading.
     **/
    _progress: function(bytes_loaded, bytes_total)
    {
        this.old_bytes_loaded = this.bytes_loaded;
        this.old_bytes_total = this.bytes_total;

        this.bytes_loaded = bytes_loaded;
        this.bytes_total = bytes_total;

        if (this.owner && this.bytes_loaded - this.old_bytes_loaded > 0) {
            this.owner._progress(this.bytes_loaded - this.old_bytes_loaded,
                                 this.bytes_total - this.old_bytes_total);
        }

        this.onProgress(bytes_loaded, bytes_total);
    },


    /**
     * lily.preloader.AbstractResource#_finish() -> undefined
     * 
     * Internally called when this resource finishes loading to signal the
     * owner [[lily.preloader.Loader]] object about the completion.
     * 
     * It also calls the [[lily.preloader.AbstractResource.onFinish]] callback.
     **/
    _finish: function()
    {
        if (this.owner) {
            this.owner._loaded(this);
        }
        this.onFinish();
    },


    /**
     * lily.preloader.AbstractResource.start() -> undefined
     * 
     * Starts tracking and loading this resource.
     **/
    start: function()
    {
        /* void */
    }
});


/**
 * class lily.preloader.ImageResource < lily.preloader.AbstractResource
 * 
 * Tracks and preloads an image resource.
 **/
lily.preloader.ImageResource = Class.create(lily.preloader.AbstractResource,
{
    /**
     * lily.preloader.ImageResource.__kind__ = "Image"
     * 
     * The kind of resource handled by this class.
     **/
    __kind__: "Image",

    /**
     * new lily.preloader.ImageResource(preloader, url[, on_progress[, on_finish[, size]]])
     * - prealoder ([[lily.preloader.Loader]]): owner Loader object.
     * - url (String): url of the image to load.
     * - on_progress (Function): callback for each update of data received.
     * - on_finish (Function): callback for when the resource finishes loading.
     * - size (Array): array with the size of the image being loaded.
     **/
    initialize: function($super, preloader, url, on_progress, on_finish, size)
    {
        $super(preloader, on_progress, on_finish);

        /**
         * lily.preloader.ImageResource#id -> Number (read only)
         * 
         * An unique number that identifies this instance.
         **/
        this.id = lily.uniqueID();

        /**
         * lily.preloader.ImageResource#url -> String
         * 
         * A string with the url of the image represented by this class.
         **/
        this.url = url;

        /**
         * lily.preloader.ImageResource#size -> Array
         * 
         * The size of the image resource represented by this class, as a
         * `[width, height]` array.
         **/
        this.size = size;
    },


    /**
     * lily.preloader.ImageResource#start() -> undefined
     * 
     * Starts tracking and loading this resource.
     **/
    start: function($super)
    {
        if (this.size && this.size.length == 2) {
            this.image = new Image(this.size[0], this.size[1]);
        } else {
            this.image = new Image();
        }
        this.image.onload = this._finish();
        this.image.src = this.url;
    }
});


/**
 * class lily.preloader.Loader
 * 
 * Tracks all resources in-game.
 **/
lily.preloader.Loader = Class.create(
{
    /**
     * new lily.preloader.Loader([options])
     * - options: An optional object passed as argument to define some options
     * for the [[lily.preloader.Loader]] object.
     **/
    initialize: function()
    {
        var options = Object.extend(
            {
                on_resource_loaded: function(resource){},
                on_finish_loading : function(){},
                on_progress       : function(preloader){}
            }, arguments[0] || {});


        /**
         * lily.preloader.Loader#_start_time -> Date
         * 
         * When the resources started bein loaded.
         **/
        this._started_time = null;

        /**
         * lily.preloader.Loader#_resources -> Object
         * 
         * An object with the resources being handled by this object.
         **/
        this._resources = {};

        /**
         * lily.preloader.Loader#_resource_list -> Array
         * 
         * A list of resources we need to preload.
         **/
        this._resource_list = [];

        /**
         * lily.preloader.Loader#bytes_loaded -> Number
         * 
         * Number of bytes loaded from all the resources within this object.
         **/
        this.bytes_loaded = 0;

        /**
         * lily.preloader.Loader#bytes_total -> Number
         * 
         * Size of all the resources within this object.
         **/
        this.bytes_total = 0;

        /**
         * lily.preloader.Loader#total_resources -> Number
         * 
         * Number of resources being handled by this object.
         **/
        this.total_resources = 0;

        /**
         * lily.preloader.Loader#loaded_resources -> Number
         * 
         * Number of resources that has been loaded.
         **/
        this.loaded_resources = 0;


        /**
         * lily.preloader.Loader#onResourceLoaded -> Function
         * 
         * A callback that is called whenever a resource finishes
         * loading. It expects only one parameter: an instance of
         * AbstractResource containing the resource that has been
         * loaded.
         **/
        this.onResourceLoaded = options.on_resource_loaded;

        /**
         * lily.preloader.Loader#onFinishLoading -> Function
         * 
         * A callback that is called with no arguments when **all**
         * resources finish loading.
         **/
        this.onFinishLoading = options.on_finish_loading;

        /**
         * lily.preloader.Loader#onProgress -> Function
         * 
         * A callback that is called whenever a resource reports loading
         * progress. It expects only one parameter: the current instance
         * of this object.
         **/
        this.onProgress = options.on_progress;
    },

    /**
     * lily.preloader.Loader#_progress(bytes_loaded, bytes_total) -> undefined
     * 
     * Internally called at each update of received data (i.e.: new chunks of
     * data being). It's a bit implementation-depended on how each resource is
     * handled.
     **/
    _progress: function(bytes_loaded, bytes_total)
    {
        this.bytes_loaded += bytes_loaded;
        this.bytes_total += bytes_total;

        this.onProgress(this);
    },


    /**
     * lily.preloader.Loader#_loaded(resource) -> undefined
     * 
     * Internally called when a resource finishes loading.
     **/
    _loaded: function(resource)
    {
        var is_loaded = this._resources[resource.id];

        if (!is_loaded) {
            this._resources[resource.id] = true;
            this.loaded_resources++;
            this.onResourceLoaded(resource);

            if (!this.finished && this.loaded_resources >= this.total_resources) {
                this.finished = true;
                this.onFinishLoading();
            }
        }
    },


    /**
     * lily.preloader.Loader#start() -> undefined
     * 
     * Starts loading the resources.
     **/
    start: function()
    {
        if (!this.start_time) {
            this.start_time = new Date();
        }

        this.nextResource();
    },


    /**
     * lily.preloader.Loader#nextResource() -> undefined
     * 
     * Preloads the next resource in the list, and schedulles to continue
     * peloading until the queue is empty.
     **/
    nextResource: function()
    {
        var resource = this._resource_list.shift();

        resource.start();
        this._resources[resource.id] = false;
        if (this._resource_list.length > 0) {
            this.nextResource.bind(this).defer();
        }
    },


    /**
     * lily.preloader.Loader#getBPS() -> Number
     * 
     * Returns the average loading speed for the resources in this object.
     **/
    getBPS: function()
    {
        if (!this.start_time) {
            return 0;
        }

        var now = new Date();
        return bytes_loaded / (now - this.start_time) * 1000;
    },


    /**
     * lily.preloader.Loader#addResource(resource) -> AbstractResource
     * 
     * Tracks an abstract resource. The resouce can be any object derived from
     * AbstractResource class.
     **/
    addResource: function(resource)
    {
        if (this._resources[resource.id] === undefined) {
            this.total_resources++;
            this._resource_list.push(resource);
        }
        return resource;
    },


    /**
     * lily.preloader.Loader#addImage(url[, size]) -> ImageResource
     * 
     * Loads and track an image resource.
     **/
    addImage: function(url, size)
    {
        return this.addResource(new lily.preloader.ImageResource(this, url,
                size));
    }
});